/* 
 * Copyright 2010 Ziya Guven Koylu, Ozge Koylu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.koylu.caffein.model.caffein;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javassist.util.proxy.ProxyFactory;

import javax.sql.DataSource;

import org.apache.commons.digester.Digester;

import com.koylu.caffein.Caffein;
import com.koylu.caffein.CaffeinFactory;
import com.koylu.caffein.ObjectToQuery;
import com.koylu.caffein.cache.CacheManager;
import com.koylu.caffein.datasource.GenericDataSource;
import com.koylu.caffein.exception.CaffeinConfigException;
import com.koylu.caffein.javassist.BeanGenerator;
import com.koylu.caffein.lock.GenericLockManager;
import com.koylu.caffein.lock.LockManager;
import com.koylu.caffein.model.clazz.Clazz;
import com.koylu.caffein.model.clazz.ForeignKey;
import com.koylu.caffein.model.clazz.Index;
import com.koylu.caffein.model.clazz.Key;
import com.koylu.caffein.model.clazz.ManyToMany;
import com.koylu.caffein.model.clazz.ManyToOne;
import com.koylu.caffein.model.clazz.OneToMany;
import com.koylu.caffein.model.clazz.OneToOne;
import com.koylu.caffein.model.clazz.OptimisticLockProperty;
import com.koylu.caffein.model.clazz.Property;
import com.koylu.caffein.model.clazz.QueryProperty;
import com.koylu.caffein.model.clazz.id.AssignedId;
import com.koylu.caffein.model.clazz.id.AutoIncrementId;
import com.koylu.caffein.model.clazz.id.CompositeId;
import com.koylu.caffein.model.clazz.id.ForeignId;
import com.koylu.caffein.model.clazz.id.Id;
import com.koylu.caffein.model.clazz.id.SequenceId;
import com.koylu.caffein.model.clazz.validation.Validation;
import com.koylu.caffein.proc.Param;
import com.koylu.caffein.proc.Proc;
import com.koylu.caffein.proc.ReturnParam;
import com.koylu.caffein.query.GenericQuery;
import com.koylu.caffein.query.MsSqlQuery;
import com.koylu.caffein.query.MySQLQuery;
import com.koylu.caffein.query.OracleQuery;
import com.koylu.caffein.query.Query;
import com.koylu.caffein.query.filter.And;
import com.koylu.caffein.query.filter.Between;
import com.koylu.caffein.query.filter.Eq;
import com.koylu.caffein.query.filter.Gt;
import com.koylu.caffein.query.filter.Gte;
import com.koylu.caffein.query.filter.In;
import com.koylu.caffein.query.filter.IsNull;
import com.koylu.caffein.query.filter.Like;
import com.koylu.caffein.query.filter.Lt;
import com.koylu.caffein.query.filter.Lte;
import com.koylu.caffein.query.filter.Neq;
import com.koylu.caffein.query.filter.NotNull;
import com.koylu.caffein.query.filter.Or;
import com.koylu.caffein.query.group.Group;
import com.koylu.caffein.query.join.FullJoin;
import com.koylu.caffein.query.join.InnerJoin;
import com.koylu.caffein.query.join.LeftJoin;
import com.koylu.caffein.query.join.RightJoin;
import com.koylu.caffein.query.order.Order;
import com.koylu.caffein.query.target.Avg;
import com.koylu.caffein.query.target.Column;
import com.koylu.caffein.query.target.Composite;
import com.koylu.caffein.query.target.Composite.CompositeField;
import com.koylu.caffein.query.target.Count;
import com.koylu.caffein.query.target.Max;
import com.koylu.caffein.query.target.Min;
import com.koylu.caffein.query.target.Sum;

public class CaffeinConfig {

	private static Digester clazzDigester;
	private static Digester procDigester;
	private Scope scope = Scope.SINGLETON;
	private Caffein caffein;
	private Class<? extends Caffein> caffeinClass = Caffein.class;
	//Tables
	private Map<String, Map<String, Integer>> columnDataTypes = new HashMap<String, Map<String, Integer>>();
	private Map<String, Map<String, Boolean>> columnNullables = new HashMap<String, Map<String, Boolean>>();
	//Procedures
	private Map<String, Map<String, Integer>> procColumnTypes = new HashMap<String, Map<String, Integer>>();
	private Map<String, Map<String, Integer>> procColumnDataTypes = new HashMap<String, Map<String, Integer>>();
	private Map<String, Map<String, Boolean>> procColumnNullables = new HashMap<String, Map<String, Boolean>>();

	private DataSource dataSource;
	private ObjectToQuery objectToQuery = new ObjectToQuery();
	private Class<? extends Query> queryClass;
	private Boolean enableQueryLogging = Boolean.FALSE;
	private Boolean supportsGetGeneratedKeys = null;
	private Map<String, Clazz> clazzMap = new HashMap<String, Clazz>();
	private Map<String, Proc> procMap = new HashMap<String, Proc>();
	private static Map<String, Class<? extends Query>> queryMap;
	private CacheManager cacheManager;
	private LockManager lockManager = new GenericLockManager(30);
	private static ClassLoader classLoaderForJavassist = null;

	static {
		queryMap = new HashMap<String, Class<? extends Query>>();
		queryMap.put("MySQL".toUpperCase(Locale.ENGLISH), MySQLQuery.class);
		queryMap.put("Oracle".toUpperCase(Locale.ENGLISH), OracleQuery.class);
		queryMap.put("Microsoft SQL Server".toUpperCase(Locale.ENGLISH), MsSqlQuery.class);

		clazzDigester = new Digester();
		clazzDigester.addObjectCreate("*/clazz", Clazz.class);
		clazzDigester.addSetProperties("*/clazz", "name", "name");
		clazzDigester.addSetProperties("*/clazz", "type", "type");
		clazzDigester.addSetProperties("*/clazz", "table", "table");
		clazzDigester.addSetProperties("*/clazz", "schema", "schema");
		clazzDigester.addSetProperties("*/clazz", "cacheable", "cacheable");
		clazzDigester.addSetProperties("*/clazz", "readonly", "readonly");

		clazzDigester.addObjectCreate("*/autoincrement-id", AutoIncrementId.class);
		clazzDigester.addSetProperties("*/autoincrement-id", "name", "name");
		clazzDigester.addSetProperties("*/autoincrement-id", "column", "column");
		clazzDigester.addSetProperties("*/autoincrement-id", "type", "type");
		clazzDigester.addSetProperties("*/autoincrement-id", "column-type", "columnType");
		clazzDigester.addSetProperties("*/autoincrement-id", "converter", "converter");
		clazzDigester.addSetNext("*/autoincrement-id", "addChild");

		clazzDigester.addObjectCreate("*/sequence-id", SequenceId.class);
		clazzDigester.addSetProperties("*/sequence-id", "name", "name");
		clazzDigester.addSetProperties("*/sequence-id", "column", "column");
		clazzDigester.addSetProperties("*/sequence-id", "sequence", "sequence");
		clazzDigester.addSetProperties("*/sequence-id", "type", "type");
		clazzDigester.addSetProperties("*/sequence-id", "column-type", "columnType");
		clazzDigester.addSetProperties("*/sequence-id", "converter", "converter");
		clazzDigester.addSetNext("*/sequence-id", "addChild");

		clazzDigester.addObjectCreate("*/assigned-id", AssignedId.class);
		clazzDigester.addSetProperties("*/assigned-id", "name", "name");
		clazzDigester.addSetProperties("*/assigned-id", "column", "column");
		clazzDigester.addSetProperties("*/assigned-id", "type", "type");
		clazzDigester.addSetProperties("*/assigned-id", "column-type", "columnType");
		clazzDigester.addSetProperties("*/assigned-id", "converter", "converter");
		clazzDigester.addSetNext("*/assigned-id", "addChild");

		clazzDigester.addObjectCreate("*/composite-id", CompositeId.class);
		clazzDigester.addSetProperties("*/composite-id", "name", "name");
		clazzDigester.addSetProperties("*/composite-id", "type", "type");
		clazzDigester.addSetNext("*/composite-id", "addChild");

		clazzDigester.addObjectCreate("*/foreign-id", ForeignId.class);
		clazzDigester.addSetProperties("*/foreign-id", "name", "name");
		clazzDigester.addSetProperties("*/foreign-id", "clazz", "clazz");
		clazzDigester.addSetNext("*/foreign-id", "addChild");

		clazzDigester.addObjectCreate("*/foreign-key", ForeignKey.class);
		clazzDigester.addSetProperties("*/foreign-key", "column", "column");
		clazzDigester.addSetProperties("*/foreign-key", "ref-column", "refColumn");
		clazzDigester.addSetProperties("*/foreign-key", "column-type", "columnType");
		clazzDigester.addSetProperties("*/foreign-key", "column-nullable", "columnNullable");
		clazzDigester.addSetNext("*/foreign-key", "addChild");

		clazzDigester.addObjectCreate("*/key", Key.class);
		clazzDigester.addSetProperties("*/key", "column", "column");
		clazzDigester.addSetProperties("*/key", "ref-column", "refColumn");
		clazzDigester.addSetProperties("*/key", "column-type", "columnType");
		clazzDigester.addSetProperties("*/key", "column-nullable", "columnNullable");
		clazzDigester.addSetNext("*/key", "addChild");

		clazzDigester.addObjectCreate("*/property", com.koylu.caffein.model.clazz.Property.class);
		clazzDigester.addSetProperties("*/property", "name", "name");
		clazzDigester.addSetProperties("*/property", "column", "column");
		clazzDigester.addSetProperties("*/property", "type", "type");
		clazzDigester.addSetProperties("*/property", "column-type", "columnType");
		clazzDigester.addSetProperties("*/property", "column-nullable", "columnNullable");
		clazzDigester.addSetProperties("*/property", "converter", "converter");
		clazzDigester.addSetNext("*/property", "addChild");

		clazzDigester.addObjectCreate("*/optimistic-lock-property", OptimisticLockProperty.class);
		clazzDigester.addSetProperties("*/optimistic-lock-property", "column", "column");
		clazzDigester.addSetNext("*/optimistic-lock-property", "addChild");

		clazzDigester.addObjectCreate("*/query-property", QueryProperty.class);
		clazzDigester.addSetProperties("*/query-property", "clazz", "clazz");
		clazzDigester.addSetProperties("*/query-property", "name", "name");
		clazzDigester.addSetProperties("*/query-property", "alias", "alias");
		clazzDigester.addSetProperties("*/query-property", "firstResult", "firstResult");
		clazzDigester.addSetProperties("*/query-property", "maxResult", "maxResult");
		clazzDigester.addSetNext("*/query-property", "addChild");

		//InnerJoin
		clazzDigester.addObjectCreate("*/inner-join", InnerJoin.class);
		clazzDigester.addSetProperties("*/inner-join", "alias", "alias");
		clazzDigester.addSetProperties("*/inner-join", "on", "on");
		clazzDigester.addSetNext("*/inner-join", "addJoin");

		//FullJoin
		clazzDigester.addObjectCreate("*/full-join", FullJoin.class);
		clazzDigester.addSetProperties("*/full-join", "alias", "alias");
		clazzDigester.addSetProperties("*/full-join", "on", "on");
		clazzDigester.addSetNext("*/full-join", "addJoin");

		//RightJoin
		clazzDigester.addObjectCreate("*/right-join", RightJoin.class);
		clazzDigester.addSetProperties("*/right-join", "alias", "alias");
		clazzDigester.addSetProperties("*/right-join", "on", "on");
		clazzDigester.addSetNext("*/right-join", "addJoin");

		//LeftJoin
		clazzDigester.addObjectCreate("*/left-join", LeftJoin.class);
		clazzDigester.addSetProperties("*/left-join", "alias", "alias");
		clazzDigester.addSetProperties("*/left-join", "on", "on");
		clazzDigester.addSetNext("*/left-join", "addJoin");

		//And
		clazzDigester.addObjectCreate("*/and", And.class);
		clazzDigester.addSetNext("*/and", "setCompositeFilter");
		//Or
		clazzDigester.addObjectCreate("*/or", Or.class);
		clazzDigester.addSetNext("*/or", "setCompositeFilter");
		//Eq
		clazzDigester.addObjectCreate("*/eq", Eq.class);
		clazzDigester.addSetProperties("*/eq", "alias", "alias");
		clazzDigester.addSetProperties("*/eq", "path", "path");
		clazzDigester.addSetProperties("*/eq", "value", "value");
		clazzDigester.addSetNext("*/eq", "addFilter");
		//Between
		clazzDigester.addObjectCreate("*/between", Between.class);
		clazzDigester.addSetProperties("*/between", "alias", "alias");
		clazzDigester.addSetProperties("*/between", "path", "path");
		clazzDigester.addSetProperties("*/between", "firstValue", "firstValue");
		clazzDigester.addSetProperties("*/between", "secondValue", "secondValue");
		clazzDigester.addSetNext("*/between", "addFilter");
		//Gt
		clazzDigester.addObjectCreate("*/gt", Gt.class);
		clazzDigester.addSetProperties("*/gt", "alias", "alias");
		clazzDigester.addSetProperties("*/gt", "path", "path");
		clazzDigester.addSetProperties("*/gt", "value", "value");
		clazzDigester.addSetNext("*/gt", "addFilter");
		//Gte
		clazzDigester.addObjectCreate("*/gte", Gte.class);
		clazzDigester.addSetProperties("*/gte", "alias", "alias");
		clazzDigester.addSetProperties("*/gte", "path", "path");
		clazzDigester.addSetProperties("*/gte", "value", "value");
		clazzDigester.addSetNext("*/gte", "addFilter");
		//In
		clazzDigester.addObjectCreate("*/in", In.class);
		clazzDigester.addSetProperties("*/in", "alias", "alias");
		clazzDigester.addSetProperties("*/in", "path", "path");
		clazzDigester.addSetProperties("*/in", "value", "value");
		clazzDigester.addSetNext("*/in", "addFilter");
		//IsNull
		clazzDigester.addObjectCreate("*/isnull", IsNull.class);
		clazzDigester.addSetProperties("*/isnull", "alias", "alias");
		clazzDigester.addSetProperties("*/isnull", "path", "path");
		clazzDigester.addSetNext("*/isnull", "addFilter");
		//Like
		clazzDigester.addObjectCreate("*/like", Like.class);
		clazzDigester.addSetProperties("*/like", "alias", "alias");
		clazzDigester.addSetProperties("*/like", "path", "path");
		clazzDigester.addSetProperties("*/like", "value", "value");
		clazzDigester.addSetNext("*/like", "addFilter");
		//Lt
		clazzDigester.addObjectCreate("*/lt", Lt.class);
		clazzDigester.addSetProperties("*/lt", "alias", "alias");
		clazzDigester.addSetProperties("*/lt", "path", "path");
		clazzDigester.addSetProperties("*/lt", "value", "value");
		clazzDigester.addSetNext("*/lt", "addFilter");
		//Lte
		clazzDigester.addObjectCreate("*/lte", Lte.class);
		clazzDigester.addSetProperties("*/lte", "alias", "alias");
		clazzDigester.addSetProperties("*/lte", "path", "path");
		clazzDigester.addSetProperties("*/lte", "value", "value");
		clazzDigester.addSetNext("*/lte", "addFilter");
		//Neq
		clazzDigester.addObjectCreate("*/neq", Neq.class);
		clazzDigester.addSetProperties("*/neq", "alias", "alias");
		clazzDigester.addSetProperties("*/neq", "path", "path");
		clazzDigester.addSetProperties("*/neq", "value", "value");
		clazzDigester.addSetNext("*/neq", "addFilter");
		//NotNull
		clazzDigester.addObjectCreate("*/notnull", NotNull.class);
		clazzDigester.addSetProperties("*/notnull", "alias", "alias");
		clazzDigester.addSetProperties("*/notnull", "path", "path");
		clazzDigester.addSetNext("*/notnull", "addFilter");

		//Target-clazz
		clazzDigester.addObjectCreate("*/target-clazz", com.koylu.caffein.query.target.Clazz.class);
		clazzDigester.addSetProperties("*/target-clazz", "alias", "alias");
		clazzDigester.addSetNext("*/target-clazz", "setTarget");
		//Target-composite
		clazzDigester.addObjectCreate("*/target-composite", Composite.class);
		clazzDigester.addSetNext("*/target-composite", "setTarget");

		//Composite-Field
		clazzDigester.addObjectCreate("*/field", CompositeField.class);
		clazzDigester.addSetProperties("*/field", "name", "name");
		clazzDigester.addSetProperties("*/field", "distinct", "distinct");
		clazzDigester.addSetNext("*/field", "addCompositeField");

		//Target-column
		clazzDigester.addObjectCreate("*/target-column", Column.class);
		clazzDigester.addSetProperties("*/target-column", "alias", "alias");
		clazzDigester.addSetProperties("*/target-column", "path", "path");
		clazzDigester.addSetNext("*/target-column", "setTarget");
		//Target-min
		clazzDigester.addObjectCreate("*/target-min", Min.class);
		clazzDigester.addSetProperties("*/target-min", "alias", "alias");
		clazzDigester.addSetProperties("*/target-min", "path", "path");
		clazzDigester.addSetProperties("*/target-min", "columnAlias", "columnAlias");
		clazzDigester.addSetNext("*/target-min", "setTarget");
		//Target-max
		clazzDigester.addObjectCreate("*/target-max", Max.class);
		clazzDigester.addSetProperties("*/target-max", "alias", "alias");
		clazzDigester.addSetProperties("*/target-max", "path", "path");
		clazzDigester.addSetProperties("*/target-max", "columnAlias", "columnAlias");
		clazzDigester.addSetNext("*/target-max", "setTarget");
		//Target-sum
		clazzDigester.addObjectCreate("*/target-sum", Sum.class);
		clazzDigester.addSetProperties("*/target-sum", "alias", "alias");
		clazzDigester.addSetProperties("*/target-sum", "path", "path");
		clazzDigester.addSetProperties("*/target-sum", "columnAlias", "columnAlias");
		clazzDigester.addSetNext("*/target-sum", "setTarget");
		//Target-avg
		clazzDigester.addObjectCreate("*/target-avg", Avg.class);
		clazzDigester.addSetProperties("*/target-avg", "alias", "alias");
		clazzDigester.addSetProperties("*/target-avg", "path", "path");
		clazzDigester.addSetProperties("*/target-avg", "columnAlias", "columnAlias");
		clazzDigester.addSetNext("*/target-avg", "setTarget");
		//Target-count
		clazzDigester.addObjectCreate("*/target-count", Count.class);
		clazzDigester.addSetProperties("*/target-count", "columnAlias", "columnAlias");
		clazzDigester.addSetNext("*/target-count", "setTarget");
		//Groupby
		clazzDigester.addObjectCreate("*/group-by", Group.class);
		clazzDigester.addSetProperties("*/group-by", "alias", "alias");
		clazzDigester.addSetProperties("*/group-by", "path", "path");
		clazzDigester.addSetNext("*/group-by", "addGroupBy");
		//Orderby
		clazzDigester.addObjectCreate("*/order-by", Order.class);
		clazzDigester.addSetProperties("*/order-by", "type", "type");
		clazzDigester.addSetProperties("*/order-by", "alias", "alias");
		clazzDigester.addSetProperties("*/order-by", "path", "path");
		clazzDigester.addSetProperties("*/order-by", "columnAlias", "columnAlias");
		clazzDigester.addSetNext("*/order-by", "addOrder");

		clazzDigester.addObjectCreate("*/many-to-one", ManyToOne.class);
		clazzDigester.addSetProperties("*/many-to-one", "name", "name");
		clazzDigester.addSetProperties("*/many-to-one", "clazz", "clazz");
		clazzDigester.addSetProperties("*/many-to-one", "cascade", "cascade");
		clazzDigester.addSetNext("*/many-to-one", "addChild");

		clazzDigester.addObjectCreate("*/one-to-one", OneToOne.class);
		clazzDigester.addSetProperties("*/one-to-one", "name", "name");
		clazzDigester.addSetProperties("*/one-to-one", "clazz", "clazz");
		clazzDigester.addSetProperties("*/one-to-one", "cascade", "cascade");
		clazzDigester.addSetNext("*/one-to-one", "addChild");

		clazzDigester.addObjectCreate("*/one-to-many", OneToMany.class);
		clazzDigester.addSetProperties("*/one-to-many", "name", "name");
		clazzDigester.addSetProperties("*/one-to-many", "clazz", "clazz");
		clazzDigester.addSetProperties("*/one-to-many", "cascade", "cascade");
		clazzDigester.addSetNext("*/one-to-many", "addChild");

		clazzDigester.addObjectCreate("*/many-to-many", ManyToMany.class);
		clazzDigester.addSetProperties("*/many-to-many", "name", "name");
		clazzDigester.addSetProperties("*/many-to-many", "clazz", "clazz");
		clazzDigester.addSetProperties("*/many-to-many", "table", "table");
		clazzDigester.addSetProperties("*/many-to-many", "cascade", "cascade");
		clazzDigester.addSetNext("*/many-to-many", "addChild");

		clazzDigester.addObjectCreate("*/index", Index.class);
		clazzDigester.addSetProperties("*/index", "column", "column");
		clazzDigester.addSetNext("*/index", "setIndex");

		clazzDigester.addObjectCreate("*/validations", ArrayList.class);
		clazzDigester.addSetNext("*/validations", "setValidations");

		clazzDigester.addObjectCreate("*/validation", Validation.class);
		clazzDigester.addSetProperties("*/validation", "name", "name");
		clazzDigester.addSetProperties("*/validation", "validate-for", "validateFor");
		clazzDigester.addSetProperties("*/validation", "min", "min");
		clazzDigester.addSetProperties("*/validation", "max", "max");
		clazzDigester.addSetProperties("*/validation", "mask", "mask");
		clazzDigester.addSetProperties("*/validation", "message", "message");
		clazzDigester.addSetNext("*/validation", "add");

		//proc
		procDigester = new Digester();
		procDigester.addObjectCreate("*/proc", Proc.class);
		procDigester.addSetProperties("*/proc", "name", "name");
		procDigester.addSetProperties("*/proc", "type", "type");
		procDigester.addSetProperties("*/proc", "schema", "schema");

		//return-param
		procDigester.addObjectCreate("*/return-param", ReturnParam.class);
		procDigester.addSetProperties("*/return-param", "name", "name");
		procDigester.addSetProperties("*/return-param", "type", "type");
		procDigester.addSetProperties("*/return-param", "column-data-type", "columnDataType");
		procDigester.addSetProperties("*/return-param", "converter", "converter");
		procDigester.addSetNext("*/return-param", "addParam");

		//param
		procDigester.addObjectCreate("*/param", Param.class);
		procDigester.addSetProperties("*/param", "name", "name");
		procDigester.addSetProperties("*/param", "column", "column");
		procDigester.addSetProperties("*/param", "type", "type");
		procDigester.addSetProperties("*/param", "column-type", "columnType");
		procDigester.addSetProperties("*/param", "column-data-type", "columnDataType");
		procDigester.addSetProperties("*/param", "column-nullable", "columnNullable");
		procDigester.addSetProperties("*/param", "converter", "converter");
		procDigester.addSetNext("*/param", "addParam");
	}

	public CaffeinConfig() {
		setClassLoaderForJavassist(getClass().getClassLoader());
	}

	public CaffeinConfig(String driver, String url, String user, String password) {
		setClassLoaderForJavassist(getClass().getClassLoader());
		GenericDataSource dataSource = new GenericDataSource();
		dataSource.setDriver(driver);
		dataSource.setUrl(url);
		dataSource.setUser(user);
		dataSource.setPassword(password);
		setEnableQueryLogging(true);
		setDataSource(dataSource);
	}

	public static void setClassLoaderForJavassist(ClassLoader classLoader) {
		classLoaderForJavassist = classLoader;
		ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() {
			public ClassLoader get(ProxyFactory pf) {
				return classLoaderForJavassist;
			}
		};
	}

	public Caffein getCaffein() throws Exception {
		if (Scope.SINGLETON.equals(scope)) {//Scope.SINGLETON
			if (caffein == null) {
				caffein = caffeinClass.newInstance();
				caffein.setCaffeinConfig(this);
			}
		} else {//Scope.PROTOTYPE
			caffein = caffeinClass.newInstance();
			caffein.setCaffeinConfig(this);
		}
		return caffein;
	}

	public Query createQuery(Clazz clazz, String alias) throws Exception {
		if (queryClass == null) {
			Connection connection = getConnection();
			if (connection != null) {
				queryClass = queryMap.get(connection.getMetaData().getDatabaseProductName().toUpperCase(Locale.ENGLISH));
			}
			if (queryClass == null) {
				queryClass = GenericQuery.class;
			}
			if (connection != null) {
				try {
					connection.close();
				} catch (Exception e) {
				}
			}
		}
		return queryClass.getDeclaredConstructor(CaffeinConfig.class, Clazz.class, String.class).newInstance(this, clazz, alias);
	}

	public void addClazz(Clazz clazz) {
		clazzMap.put(clazz.getName(), clazz);
		String type = clazz.getType();
		if (type != null && !"".equals(type)) {
			clazzMap.put(type, clazz);
		}
	}

	public void addProc(Proc proc) {
		procMap.put(proc.getName(), proc);
		String type = proc.getType();
		if (type != null && !"".equals(type)) {
			procMap.put(type, proc);
		}
	}

	public Clazz getClazz(String type) throws Exception {
		if (clazzMap.containsKey(type)) {
			return clazzMap.get(type);
		}
		int indexOf = type.indexOf("_$$_");
		if (indexOf > -1) {// For Proxy Beans
			String realType = type.substring(0, indexOf);
			if (clazzMap.containsKey(realType)) {
				Clazz clazz = clazzMap.get(realType);
				clazzMap.put(type, clazz);
				return clazz;
			}
		}
		return null;
	}

	public Proc getProc(String type) throws Exception {
		if (procMap.containsKey(type)) {
			return procMap.get(type);
		}
		int indexOf = type.indexOf("_$$_");
		if (indexOf > -1) {// For Proxy Beans
			String realType = type.substring(0, indexOf);
			if (procMap.containsKey(realType)) {
				Proc proc = procMap.get(realType);
				procMap.put(type, proc);
				return proc;
			}
		}
		return null;
	}

	public Clazz generateSimpleClazz(String table) throws Exception {
		return generateSimpleClazz(table, null);
	}

	public Clazz generateSimpleClazz(String table, String sequenceName) throws Exception {
		String schema = null;
		String tableWithoutSchema = null;
		if (table.contains(".")) {
			schema = table.substring(0, table.indexOf("."));
			tableWithoutSchema = table.substring(table.indexOf(".") + 1, table.length());
		} else {
			tableWithoutSchema = table;
		}
		Clazz clazz = clazzMap.get(table);
		if (clazz == null) {
			clazz = new Clazz();
			clazz.setName(tableWithoutSchema);
			clazz.setSchema(schema);
			clazz.setTable(tableWithoutSchema);
			Connection connection = null;
			ResultSet resultSet = null;
			try {
				connection = getConnection();
				boolean view = isView(connection, table);
				clazz.setReadonly(view);
				DatabaseMetaData databaseMetaData = connection.getMetaData();
				Set<String> fields = new HashSet<String>();
				resultSet = databaseMetaData.getColumns(null, schema, tableWithoutSchema, null);
				while (resultSet.next()) {
					fields.add(resultSet.getString("COLUMN_NAME"));
				}
				Set<String> keys = new HashSet<String>();
				if (!view) {
					databaseMetaData = connection.getMetaData();
					resultSet = databaseMetaData.getPrimaryKeys(null, schema, tableWithoutSchema);
					while (resultSet.next()) {
						String column = resultSet.getString("COLUMN_NAME");
						keys.add(column);
						fields.remove(column);
					}
				} else {
					Iterator<String> iterator = fields.iterator();
					keys.add(iterator.next());
					iterator.remove();
				}
				Id id = null;
				if (keys.size() > 1) {
					id = new CompositeId();
					id.setName("id");
					for (String column : keys) {
						Property property = new Property();
						property.setName(getFieldName(column));
						property.setColumn(column);
						((CompositeId) id).addChild(property);
					}
				} else {
					String column = keys.iterator().next();
					if (isColumnAutoIncrement(connection, table, column)) {
						id = new AutoIncrementId();
					} else {
						if (sequenceName == null) {
							id = new AssignedId();
						} else {
							id = new SequenceId();
							((SequenceId) id).setSequence(sequenceName);
						}
					}
					id.setName(getFieldName(column));
					((Property) id).setColumn(column);
				}
				clazz.addChild(id);
				for (String column : fields) {
					Property property = new Property();
					property.setName(getFieldName(column));
					property.setColumn(column);
					clazz.addChild(property);
				}
				clazz.prepareColumnMap();
				addClazz(clazz);
			} catch (Exception e) {
				throw e;
			} finally {
				if (resultSet != null) {
					try {
						resultSet.close();
					} catch (Exception ignored) {
					}
				}
				if (connection != null) {
					try {
						connection.close();
					} catch (Exception ignored) {
					}
				}
			}
		}
		return clazz;
	}

	public Proc generateProc(String procedureName) throws Exception {
		String schema = null;
		String procedureNameWithoutSchema = null;
		if (procedureName.contains(".")) {
			schema = procedureName.substring(0, procedureName.indexOf("."));
			procedureNameWithoutSchema = procedureName.substring(procedureName.indexOf(".") + 1, procedureName.length());
		} else {
			procedureNameWithoutSchema = procedureName;
		}
		Proc proc = procMap.get(procedureName);
		if (proc == null) {
			proc = new Proc();
			proc.setName(procedureNameWithoutSchema);
			proc.setSchema(schema);
			Connection connection = null;
			ResultSet resultSet = null;
			try {
				connection = getConnection();
				isProcedureExist(connection, procedureName);
				resultSet = connection.getMetaData().getProcedureColumns(null, schema, procedureNameWithoutSchema, null);
				while (resultSet.next()) {
					Param param = null;
					int columnType = Integer.parseInt(resultSet.getString("COLUMN_TYPE"));
					int columnDataType = Integer.parseInt(resultSet.getString("DATA_TYPE"));
					if (columnType == DatabaseMetaData.procedureColumnReturn) {
						param = new ReturnParam();
						param.setName(ReturnParam.DEFAULT_NAME);
					} else {
						String column = resultSet.getString("COLUMN_NAME");
						param = new Param();
						param.setName(getFieldName(column));
						param.setColumn(column);
						param.setColumnType(columnType);
						param.setColumnNullable("1".equals(resultSet.getString("NULLABLE")));
					}
					param.setColumnDataType(columnDataType);
					proc.addParam(param);
				}
				proc.prepare(this);
				addProc(proc);
			} catch (Exception e) {
				throw e;
			} finally {
				if (resultSet != null) {
					try {
						resultSet.close();
					} catch (Exception ignored) {
					}
				}
				if (connection != null) {
					try {
						connection.close();
					} catch (Exception ignored) {
					}
				}
			}
		}
		return proc;
	}

	private String replace(String str, String target, String replacement) {
		if (str == null || target == null || replacement == null) {
			return null;
		}
		int k = str.indexOf(target);
		if (k > -1) {
			return str.substring(0, k) + replacement + replace(str.substring(k + target.length(), str.length()), target, replacement);
		}
		return str;
	}

	public String getFieldName(String column) {
		column = replace(column, "@", "");
		if (column.contains("_")) {
			StringBuffer field = new StringBuffer("");
			StringTokenizer tokenizer = new StringTokenizer(column, "_");
			if (tokenizer.hasMoreTokens()) {
				field.append(tokenizer.nextToken().toLowerCase(Locale.ENGLISH));
			}
			while (tokenizer.hasMoreTokens()) {
				String token = tokenizer.nextToken();
				field.append(token.substring(0, 1).toUpperCase(Locale.ENGLISH)).append(token.substring(1, token.length()).toLowerCase(Locale.ENGLISH));
			}
			return field.toString();
		}
		return column.toLowerCase(Locale.ENGLISH);
	}

	public Map<String, Clazz> getClazzMap() {
		return clazzMap;
	}

	public Map<String, Proc> getProcMap() {
		return procMap;
	}

	public void setObjectToQuery(ObjectToQuery objectToQuery) {
		this.objectToQuery = objectToQuery;
	}

	public ObjectToQuery getObjectToQuery() {
		return objectToQuery;
	}

	public DataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public Connection getConnection() throws Exception {
		return dataSource.getConnection();
	}

	public CacheManager getCacheManager() {
		return cacheManager;
	}

	public void setCacheManager(CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}

	public LockManager getLockManager() {
		return lockManager;
	}

	public void setLockManager(LockManager lockManager) {
		this.lockManager = lockManager;
	}

	public boolean supportsGetGeneratedKeys() {
		if (supportsGetGeneratedKeys == null) {
			Connection connection = null;
			try {
				connection = getDataSource().getConnection();
				supportsGetGeneratedKeys = connection.getMetaData().supportsGetGeneratedKeys();
			} catch (Exception e) {
				supportsGetGeneratedKeys = Boolean.FALSE;
			} finally {
				if (connection != null) {
					try {
						connection.close();
					} catch (Exception e) {
					}
				}
			}
		}
		return supportsGetGeneratedKeys;
	}

	public boolean isView(Connection connection, String table) throws Exception {
		ResultSet resultSet = null;
		try {
			if (table.contains(".")) {
				resultSet = connection.getMetaData().getTables(null, table.substring(0, table.indexOf(".")), table.substring(table.indexOf(".") + 1, table.length()), null);
			} else {
				resultSet = connection.getMetaData().getTables(null, null, table, null);
			}
			if (resultSet.next()) {
				return "VIEW".equalsIgnoreCase(resultSet.getString("TABLE_TYPE"));
			} else {
				throw new RuntimeException("table or view not found:" + table);
			}
		} catch (Exception e) {
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (Exception ignored) {
				}
			}
			throw e;
		}
	}

	public boolean isColumnAutoIncrement(Connection connection, String table, String column) throws Exception {
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			preparedStatement = connection.prepareStatement("SELECT " + column + " FROM " + table);
			resultSet = preparedStatement.executeQuery();
			ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
			return resultSetMetaData.isAutoIncrement(1);
		} catch (Exception e) {
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (Exception ignored) {
				}
			}
			if (preparedStatement != null) {
				try {
					preparedStatement.close();
				} catch (Exception ignored) {
				}
			}
			throw e;
		}
	}

	public Integer getColumnType(String table, String column) throws Exception {
		Map<String, Integer> typeMap = columnDataTypes.get(table);
		if (typeMap == null) {
			loadDetailsForTable(table);
			typeMap = columnDataTypes.get(table);
			if (typeMap == null) {
				throw new Exception("table is not exist:" + table);
			}
		}
		Integer result = typeMap.get(column.toUpperCase(Locale.ENGLISH));
		if (result == null) {
			throw new Exception("column is not exist:" + column + " in the table:" + table);
		}
		return result;
	}

	public Boolean isColumnNullable(String table, String column) throws Exception {
		Map<String, Boolean> nullableMap = columnNullables.get(table);
		if (nullableMap == null) {
			loadDetailsForTable(table);
			nullableMap = columnNullables.get(table);
			if (nullableMap == null) {
				throw new Exception("table is not exist:" + table);
			}
		}
		Boolean result = nullableMap.get(column.toUpperCase(Locale.ENGLISH));
		if (result == null) {
			throw new Exception("column is not exist:" + column + " in the table:" + table);
		}
		return result;
	}

	public void loadDetailsForTable(String table) throws Exception {
		Map<String, Integer> dataTypeMap = new HashMap<String, Integer>();
		Map<String, Boolean> nullableMap = new HashMap<String, Boolean>();
		Connection connection = null;
		ResultSet resultSet = null;
		try {
			connection = getConnection();
			if (table.contains(".")) {
				resultSet = connection.getMetaData().getColumns(null, table.substring(0, table.indexOf(".")), table.substring(table.indexOf(".") + 1, table.length()), null);
			} else {
				resultSet = connection.getMetaData().getColumns(null, null, table, null);
			}
			while (resultSet.next()) {
				String column = resultSet.getString("COLUMN_NAME").toUpperCase(Locale.ENGLISH);
				dataTypeMap.put(column, Integer.parseInt(resultSet.getString("DATA_TYPE")));
				nullableMap.put(column, "1".equals(resultSet.getString("NULLABLE")));
			}
			columnDataTypes.put(table, dataTypeMap);
			columnNullables.put(table, nullableMap);
		} catch (Exception e) {
			throw e;
		} finally {
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (Exception ignored) {
				}
			}
			if (connection != null) {
				try {
					connection.close();
				} catch (Exception ignored) {
				}
			}
		}
	}

	public void isProcedureExist(Connection connection, String procedureName) throws Exception {
		ResultSet resultSet = null;
		try {
			if (procedureName.contains(".")) {
				resultSet = connection.getMetaData().getProcedures(null, procedureName.substring(0, procedureName.indexOf(".")), procedureName.substring(procedureName.indexOf(".") + 1, procedureName.length()));
			} else {
				resultSet = connection.getMetaData().getProcedures(null, null, procedureName);
			}
			if (!resultSet.next()) {
				throw new RuntimeException("procedure not found:" + procedureName);
			}
		} catch (Exception e) {
			throw e;
		} finally {
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (Exception ignored) {
				}
			}
		}
	}

	public Integer getProcColumnType(String procedureName, String column) throws Exception {
		Map<String, Integer> types = procColumnTypes.get(procedureName);
		if (types == null) {
			loadDetailsForProcedure(procedureName);
			types = procColumnTypes.get(procedureName);
			if (types == null) {
				throw new Exception("procedure is not exist:" + procedureName);
			}
		}
		Integer result = types.get(column.toUpperCase(Locale.ENGLISH));
		if (result == null) {
			throw new Exception("column is not exist:" + column + " in the procedure:" + procedureName);
		}
		return result;
	}

	public Integer getProcColumnDataType(String procedureName, String column) throws Exception {
		Map<String, Integer> dataTypes = procColumnDataTypes.get(procedureName);
		if (dataTypes == null) {
			loadDetailsForProcedure(procedureName);
			dataTypes = procColumnDataTypes.get(procedureName);
			if (dataTypes == null) {
				throw new Exception("procedure is not exist:" + procedureName);
			}
		}
		Integer result = dataTypes.get(column.toUpperCase(Locale.ENGLISH));
		if (result == null) {
			throw new Exception("column is not exist:" + column + " in the procedure:" + procedureName);
		}
		return result;
	}

	public Boolean isProcColumnNullable(String procedureName, String column) throws Exception {
		Map<String, Boolean> nullableMap = procColumnNullables.get(procedureName);
		if (nullableMap == null) {
			loadDetailsForProcedure(procedureName);
			nullableMap = procColumnNullables.get(procedureName);
			if (nullableMap == null) {
				throw new Exception("procedure is not exist:" + procedureName);
			}
		}
		Boolean result = nullableMap.get(column.toUpperCase(Locale.ENGLISH));
		if (result == null) {
			throw new Exception("column is not exist:" + column + " in the procedure:" + procedureName);
		}
		return result;
	}

	public void loadDetailsForProcedure(String procedureName) throws Exception {
		Map<String, Integer> dataTypeMap = new HashMap<String, Integer>();
		Map<String, Integer> columnTypeMap = new HashMap<String, Integer>();
		Map<String, Boolean> nullableMap = new HashMap<String, Boolean>();
		Connection connection = null;
		ResultSet resultSet = null;
		try {
			connection = getConnection();
			if (procedureName.contains(".")) {
				resultSet = connection.getMetaData().getProcedureColumns(null, procedureName.substring(0, procedureName.indexOf(".")), procedureName.substring(procedureName.indexOf(".") + 1, procedureName.length()), null);
			} else {
				resultSet = connection.getMetaData().getProcedureColumns(null, null, procedureName, null);
			}
			while (resultSet.next()) {
				String column = null;
				int columnType = Integer.parseInt(resultSet.getString("COLUMN_TYPE"));
				if (columnType == DatabaseMetaData.procedureColumnReturn) {
					column = ReturnParam.DEFAULT_COLUMN_NAME;
				} else {
					column = resultSet.getString("COLUMN_NAME").toUpperCase(Locale.ENGLISH);
				}
				dataTypeMap.put(column, Integer.parseInt(resultSet.getString("DATA_TYPE")));
				columnTypeMap.put(column, columnType);
				nullableMap.put(column, "1".equals(resultSet.getString("NULLABLE")));
			}
			procColumnTypes.put(procedureName, columnTypeMap);
			procColumnDataTypes.put(procedureName, dataTypeMap);
			procColumnNullables.put(procedureName, nullableMap);
		} catch (Exception e) {
			throw e;
		} finally {
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (Exception ignored) {
				}
			}
			if (connection != null) {
				try {
					connection.close();
				} catch (Exception ignored) {
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	public void setCaffeinClass(String clazz) throws Exception {
		try {
			caffeinClass = (Class<? extends Caffein>) Class.forName(clazz);
		} catch (Exception e) {
			throw new CaffeinConfigException("invalid caffein class:" + clazz, e);
		}
	}

	public Scope getScope() {
		return scope;
	}

	public void setCaffeinScope(String scope) {
		this.scope = Scope.valueOf(scope.toUpperCase(Locale.ENGLISH));
	}

	public void setDataSource(CaffeinConfigProperty dataSourceProperty) throws Exception {
		if (dataSource == null) {
			try {
				dataSource = (DataSource) dataSourceProperty.toObject();
			} catch (Exception e) {
				throw new CaffeinConfigException("problem at datasource definition check your caffein config file", e);
			}
		}
	}

	public void setCacheManager(CaffeinConfigProperty cacheProperty) throws Exception {
		if (cacheManager == null) {
			try {
				cacheManager = (CacheManager) cacheProperty.toObject();
				cacheManager.initiate();
			} catch (Exception e) {
				throw new CaffeinConfigException("problem at cacheManager definition check your caffein config file", e);
			}
		}
	}

	public void setLockManager(CaffeinConfigProperty lockProperty) throws Exception {
		if (lockManager == null) {
			try {
				lockManager = (LockManager) lockProperty.toObject();
				lockManager.initiate();
			} catch (Exception e) {
				throw new CaffeinConfigException("problem at lockManager definition check your caffein config file", e);
			}
		}
	}

	public boolean isEnableQueryLogging() {
		return enableQueryLogging;
	}

	public void setEnableQueryLogging(boolean enableQueryLogging) {
		this.enableQueryLogging = enableQueryLogging;
	}

	@SuppressWarnings("unchecked")
	public void setQuery(CaffeinConfigProperty queryProperty) throws Exception {
		try {
			queryClass = (Class<? extends Query>) Class.forName(queryProperty.getClazz());
		} catch (Exception e) {
			throw new CaffeinConfigException("invalid query class:" + queryProperty.getClazz(), e);
		}
	}

	public void setClazzList(List<CaffeinConfigProperty> clazzList) throws Exception {
		for (CaffeinConfigProperty clazzFile : clazzList) {
			Clazz clazz = (Clazz) CaffeinFactory.fileToObject(clazzDigester, clazzFile.getName());
			if (clazz == null) {
				throw new CaffeinConfigException("clazz file is not exist:" + clazzFile.getName());
			}
			clazz.prepareColumnMap();
			addClazz(clazz);
		}
	}

	public void setProcList(List<CaffeinConfigProperty> procList) throws Exception {
		for (CaffeinConfigProperty procFile : procList) {
			Proc proc = (Proc) CaffeinFactory.fileToObject(procDigester, procFile.getName());
			if (proc == null) {
				throw new CaffeinConfigException("proc file is not exist:" + procFile.getName());
			}
			proc.prepare(this);
			addProc(proc);
		}
	}

	public void healthCheck() throws Exception {
		for (Clazz clazz : clazzMap.values()) {
			clazz.healthCheck(this);
		}
	}

	public void shutdown() {
		if (cacheManager != null) {
			cacheManager.shutdown();
		}
		lockManager.shutdown();
		CaffeinFactory.clear();
	}

	public static Class<?> getFieldType(Class<?> clazz, String fieldName) {
		String formattedFieldName = BeanGenerator.formatField(fieldName);
		String getterFormattedFieldName = "get" + formattedFieldName;
		Method[] methods = clazz.getMethods();
		for (int i = 0; i < methods.length; i++) {
			Method method = methods[i];
			if (method.getName().equals(getterFormattedFieldName) && method.getParameterTypes().length == 0) {
				if (boolean.class != method.getReturnType()) {
					return method.getReturnType();
				}
			}
		}
		getterFormattedFieldName = "is" + formattedFieldName;
		for (int i = 0; i < methods.length; i++) {
			Method method = methods[i];
			if (method.getName().equals(getterFormattedFieldName) && method.getParameterTypes().length == 0) {
				if (boolean.class == method.getReturnType()) {
					return method.getReturnType();
				}
			}
		}
		throw new RuntimeException(fieldName + " not found in the class:" + clazz.getName());
	}
}